#ifndef LVEVENT_H_
#define LVEVENT_H_

#include "lvglpp.h"
#include "lv_cpp/core/LvIndev.h"

namespace lvglpp
{
	template <typename PROTYPE>
	class LvTObj;

	template <lv_event_code_t = LV_EVENT_ALL>
	struct LvEvent;

	template <>
	struct LvEvent<LV_EVENT_ALL> : protected lv_event_t
	{
	private:
		template <typename T>
		static constexpr auto _parseCode = LV_EVENT_PREPROCESS;

		template <typename T>
		requires requires(decltype(&T::operator())) { true; }
		static constexpr auto _parseCode<T> = _parseCode<decltype(&T::operator())>;

		template <typename R, typename T, lv_event_code_t CODE>
		static constexpr auto _parseCode<R (T::*)(LvEvent<CODE> &) const> = CODE;

		template <typename R, typename T, lv_event_code_t CODE>
		static constexpr auto _parseCode<R (T::*)(LvEvent<CODE> &)> = CODE;

		template <lv_event_code_t... CODE>
		static constexpr auto _realCode = LV_EVENT_PREPROCESS;

		template <lv_event_code_t CODE>
		static constexpr auto _realCode<CODE> = CODE;

		template <lv_event_code_t CODE>
		static constexpr auto _realCode<LV_EVENT_PREPROCESS, CODE> = CODE;

		template <lv_event_code_t CODE>
		static constexpr auto _realCode<CODE, LV_EVENT_PREPROCESS> = CODE;

		template <lv_event_code_t... CODE>
		static constexpr bool _isRealEvent = _realCode<CODE...> != LV_EVENT_PREPROCESS;

		template <typename PROTYPE>
		friend class LvTObj;

	public:
		constexpr lv_event_t &raw() noexcept { return *this; }
		/**
		 * Get the object originally targeted by the event. It's the same even if the event is bubbled.
		 * @return      the target of the event_code
		 */
		LvTObj<lv_obj_t> &getTarget() noexcept { return (LvTObj<lv_obj_t> &)*lv_event_get_target(this); };

		/**
		 * Get the current target of the event. It's the object which event handler being called.
		 * If the event is not bubbled it's the same as "normal" target.
		 * @return      pointer to the current target of the event_code
		 */
		LvTObj<lv_obj_t> &getCurrentTarget() noexcept { return (LvTObj<lv_obj_t> &)*lv_event_get_current_target(this); };

		/**
		 * Get the event code of an event
		 * @return      the event code. (E.g. `LV_EVENT_CLICKED`, `LV_EVENT_FOCUSED`, etc)
		 */
		lv_event_code_t getCode() noexcept { return lv_event_get_code(this); };

		/**
		 * Get the parameter passed when the event was sent
		 * @return      pointer to the parameter
		 */
		// void *getParam() noexcept { return lv_event_get_param(this); };

		/**
		 * Get the user_data passed when the event was registered on the object
		 * @return      pointer to the user_data
		 */
		// void *getUserData() noexcept { return lv_event_get_user_data(this); };

		/**
		 * Stop the event from bubbling.
		 * This is only valid when called in the middle of an event processing chain.
		 * @param e     pointer to the event descriptor
		 */
		LvEvent &stopBubbling() noexcept { return lv_event_stop_bubbling(this), *this; }

		/**
		 * Stop processing this event.
		 * This is only valid when called in the middle of an event processing chain.
		 * @param e     pointer to the event descriptor
		 */
		LvEvent &stopProcessing() noexcept { return lv_event_stop_processing(this), *this; }
	};

	struct LvIndevEvent : public LvEvent<>
	{
		/**
		 * Get the input device passed as parameter to indev related events.
		 * @return      the indev that triggered the event or NULL if called on a not indev related event
		 */
		LvIndev &getIndev() noexcept { return *(LvIndev *)lv_event_get_indev(this); };
	};

	/** Input device events*/

	/** The object has been pressed*/
	template <>
	struct LvEvent<LV_EVENT_PRESSED> : public LvIndevEvent
	{
	};

	/** The object is being pressed (called continuously while pressing)*/
	template <>
	struct LvEvent<LV_EVENT_PRESSING> : public LvIndevEvent
	{
	};

	/** The object is still being pressed but slid cursor/finger off of the object */
	template <>
	struct LvEvent<LV_EVENT_PRESS_LOST> : public LvIndevEvent
	{
	};

	/** The object was pressed for a short period of time, then released it. Not called if scrolled.*/
	template <>
	struct LvEvent<LV_EVENT_SHORT_CLICKED> : public LvIndevEvent
	{
	};

	/** Object has been pressed for at least `long_press_time`.  Not called if scrolled.*/
	template <>
	struct LvEvent<LV_EVENT_LONG_PRESSED> : public LvIndevEvent
	{
	};

	/** Called after `long_press_time` in every `long_press_repeat_time` ms.  Not called if scrolled.*/
	template <>
	struct LvEvent<LV_EVENT_LONG_PRESSED_REPEAT> : public LvIndevEvent
	{
	};

	/** Called on release if not scrolled (regardless to long press)*/
	template <>
	struct LvEvent<LV_EVENT_CLICKED> : public LvIndevEvent
	{
	};

	/** Called in every cases when the object has been released*/
	template <>
	struct LvEvent<LV_EVENT_RELEASED> : public LvIndevEvent
	{
	};

	/** Scrolling begins*/
	template <>
	struct LvEvent<LV_EVENT_SCROLL_BEGIN> : public LvIndevEvent
	{
		/**
		 * Get the animation descriptor of a scrolling. Can be used in `LV_EVENT_SCROLL_BEGIN`
		 * @return      the animation that will scroll the object. (can be modified as required)
		 */
		lv_anim_t &getScrollAnim() noexcept { return *lv_event_get_scroll_anim(this); };
	};

	/** Scrolling ends*/
	template <>
	struct LvEvent<LV_EVENT_SCROLL_END> : public LvIndevEvent
	{
	};

	/** Scrolling*/
	template <>
	struct LvEvent<LV_EVENT_SCROLL> : public LvIndevEvent
	{
	};

	/** A gesture is detected. Get the gesture with `lv_indev_get_gesture_dir(lv_indev_get_act());` */
	template <>
	struct LvEvent<LV_EVENT_GESTURE> : public LvIndevEvent
	{
	};

	/** A key is sent to the object. Get the key with `lv_indev_get_key(lv_indev_get_act());`*/
	template <>
	struct LvEvent<LV_EVENT_KEY> : public LvIndevEvent
	{
		/**
		 * Get the key passed as parameter to an event. Can be used in `LV_EVENT_KEY`
		 * @return      the triggering key or NULL if called on an unrelated event
		 */
		uint32_t getKey() noexcept { return lv_event_get_key(this); };
	};

	/** The object is focused*/
	template <>
	struct LvEvent<LV_EVENT_FOCUSED> : public LvIndevEvent
	{
	};

	/** The object is defocused*/
	template <>
	struct LvEvent<LV_EVENT_DEFOCUSED> : public LvIndevEvent
	{
	};

	/** The object is defocused but still selected*/
	template <>
	struct LvEvent<LV_EVENT_LEAVE> : public LvIndevEvent
	{
	};

	/** Perform advanced hit-testing*/
	template <>
	struct LvEvent<LV_EVENT_HIT_TEST> : public LvIndevEvent
	{
		/**
		 * Get a pointer to an `lv_hit_test_info_t` variable in which the hit test result should be saved. Can be used in `LV_EVENT_HIT_TEST`
		 * @return      pointer to `lv_hit_test_info_t` or NULL if called on an unrelated event
		 */
		lv_hit_test_info_t &getHitTestInfo() noexcept { return *lv_event_get_hit_test_info(this); };

		/**
		 * Get a pointer to an area which should be examined whether the object fully covers it or not.
		 * Can be used in `LV_EVENT_HIT_TEST`
		 * @return      an area with absolute coordinates to check
		 */
		const lv_area_t &getCoverArea() noexcept { return *lv_event_get_cover_area(this); };
	};

	/** Drawing events*/

	/** Check if the object fully covers an area. The event parameter is `lv_cover_check_info_t *`.*/
	template <>
	struct LvEvent<LV_EVENT_COVER_CHECK> : public LvEvent<>
	{
		/**
		 * Set the result of cover checking. Can be used in `LV_EVENT_COVER_CHECK`
		 * @param res   an element of ::lv_cover_check_info_t
		 */
		void setCoverRes(lv_cover_res_t res) noexcept { return lv_event_set_cover_res(this, res); };
	};

	/** Get the required extra draw area around the object (e.g. for shadow). The event parameter is `lv_coord_t *` to store the size.*/
	template <>
	struct LvEvent<LV_EVENT_REFR_EXT_DRAW_SIZE> : public LvEvent<>
	{
		/**
		 * Set the new extra draw size. Can be used in `LV_EVENT_REFR_EXT_DRAW_SIZE`
		 * @param size  The new extra draw size
		 */
		void setExtDrawSize(lv_coord_t size) noexcept { return lv_event_set_ext_draw_size(this, size); };
	};

	/** Starting the main drawing phase*/
	template <>
	struct LvEvent<LV_EVENT_DRAW_MAIN_BEGIN> : public LvEvent<>
	{
		/**
		 * Get the draw context which should be the first parameter of the draw functions.
		 * Namely: `LV_EVENT_DRAW_MAIN/POST`, `LV_EVENT_DRAW_MAIN/POST_BEGIN`, `LV_EVENT_DRAW_MAIN/POST_END`
		 * @return      pointer to a draw context or NULL if called on an unrelated event
		 */
		const lv_draw_ctx_t &getDrawCtx() noexcept { return *lv_event_get_draw_ctx(this); };
	};

	/** Perform the main drawing*/
	template <>
	struct LvEvent<LV_EVENT_DRAW_MAIN> : public LvEvent<>
	{
		/**
		 * Get the draw context which should be the first parameter of the draw functions.
		 * Namely: `LV_EVENT_DRAW_MAIN/POST`, `LV_EVENT_DRAW_MAIN/POST_BEGIN`, `LV_EVENT_DRAW_MAIN/POST_END`
		 * @return      pointer to a draw context or NULL if called on an unrelated event
		 */
		const lv_draw_ctx_t &getDrawCtx() noexcept { return *lv_event_get_draw_ctx(this); };
	};

	/** Finishing the main drawing phase*/
	template <>
	struct LvEvent<LV_EVENT_DRAW_MAIN_END> : public LvEvent<>
	{
		/**
		 * Get the draw context which should be the first parameter of the draw functions.
		 * Namely: `LV_EVENT_DRAW_MAIN/POST`, `LV_EVENT_DRAW_MAIN/POST_BEGIN`, `LV_EVENT_DRAW_MAIN/POST_END`
		 * @return      pointer to a draw context or NULL if called on an unrelated event
		 */
		const lv_draw_ctx_t &getDrawCtx() noexcept { return *lv_event_get_draw_ctx(this); };
	};

	/** Starting the post draw phase (when all children are drawn)*/
	template <>
	struct LvEvent<LV_EVENT_DRAW_POST_BEGIN> : public LvEvent<>
	{
		/**
		 * Get the draw context which should be the first parameter of the draw functions.
		 * Namely: `LV_EVENT_DRAW_MAIN/POST`, `LV_EVENT_DRAW_MAIN/POST_BEGIN`, `LV_EVENT_DRAW_MAIN/POST_END`
		 * @return      pointer to a draw context or NULL if called on an unrelated event
		 */
		const lv_draw_ctx_t &getDrawCtx() noexcept { return *lv_event_get_draw_ctx(this); };
	};

	/** Perform the post draw phase (when all children are drawn)*/
	template <>
	struct LvEvent<LV_EVENT_DRAW_POST> : public LvEvent<>
	{
		/**
		 * Get the draw context which should be the first parameter of the draw functions.
		 * Namely: `LV_EVENT_DRAW_MAIN/POST`, `LV_EVENT_DRAW_MAIN/POST_BEGIN`, `LV_EVENT_DRAW_MAIN/POST_END`
		 * @return      pointer to a draw context or NULL if called on an unrelated event
		 */
		const lv_draw_ctx_t &getDrawCtx() noexcept { return *lv_event_get_draw_ctx(this); };
	};

	/** Finishing the post draw phase (when all children are drawn)*/
	template <>
	struct LvEvent<LV_EVENT_DRAW_POST_END> : public LvEvent<>
	{
		/**
		 * Get the draw context which should be the first parameter of the draw functions.
		 * Namely: `LV_EVENT_DRAW_MAIN/POST`, `LV_EVENT_DRAW_MAIN/POST_BEGIN`, `LV_EVENT_DRAW_MAIN/POST_END`
		 * @return      pointer to a draw context or NULL if called on an unrelated event
		 */
		const lv_draw_ctx_t &getDrawCtx() noexcept { return *lv_event_get_draw_ctx(this); };
	};

	/** Starting to draw a part. The event parameter is `lv_obj_draw_dsc_t *`. */
	template <>
	struct LvEvent<LV_EVENT_DRAW_PART_BEGIN> : public LvEvent<>
	{
		/**
		 * Get the part draw descriptor passed as parameter to `LV_EVENT_DRAW_PART_BEGIN/END`.
		 * @return      the part draw descriptor to hook the drawing or NULL if called on an unrelated event
		 */
		lv_obj_draw_part_dsc_t &getDrawPartDsc() noexcept { return *lv_event_get_draw_part_dsc(this); };
	};

	/** Finishing to draw a part. The event parameter is `lv_obj_draw_dsc_t *`. */
	template <>
	struct LvEvent<LV_EVENT_DRAW_PART_END> : public LvEvent<>
	{
		/**
		 * Get the part draw descriptor passed as parameter to `LV_EVENT_DRAW_PART_BEGIN/END`.
		 * @return      the part draw descriptor to hook the drawing or NULL if called on an unrelated event
		 */
		lv_obj_draw_part_dsc_t &getDrawPartDsc() noexcept { return *lv_event_get_draw_part_dsc(this); };
	};

	/** Special events*/

	/** The object's value has changed (i.e. slider moved)*/
	template <>
	struct LvEvent<LV_EVENT_VALUE_CHANGED> : public LvEvent<>
	{
	};

	/** A text is inserted to the object. The event data is `char *` being inserted.*/
	template <>
	struct LvEvent<LV_EVENT_INSERT> : public LvEvent<>
	{
	};

	/** Notify the object to refresh something on it (for the user)*/
	template <>
	struct LvEvent<LV_EVENT_REFRESH> : public LvEvent<>
	{
	};

	/** A process has finished*/
	template <>
	struct LvEvent<LV_EVENT_READY> : public LvEvent<>
	{
	};

	/** A process has been cancelled */
	template <>
	struct LvEvent<LV_EVENT_CANCEL> : public LvEvent<>
	{
	};

	/** Other events*/

	/** Object is being deleted*/
	template <>
	struct LvEvent<LV_EVENT_DELETE> : public LvEvent<>
	{
	};

	/** Child was removed, added, or its size, position were changed */
	template <>
	struct LvEvent<LV_EVENT_CHILD_CHANGED> : public LvEvent<>
	{
	};

	/** Child was created, always bubbles up to all parents*/
	template <>
	struct LvEvent<LV_EVENT_CHILD_CREATED> : public LvEvent<>
	{
	};

	/** Child was deleted, always bubbles up to all parents*/
	template <>
	struct LvEvent<LV_EVENT_CHILD_DELETED> : public LvEvent<>
	{
	};

	/** A screen unload started, fired immediately when scr_load is called*/
	template <>
	struct LvEvent<LV_EVENT_SCREEN_UNLOAD_START> : public LvEvent<>
	{
	};

	/** A screen load started, fired when the screen change delay is expired*/
	template <>
	struct LvEvent<LV_EVENT_SCREEN_LOAD_START> : public LvEvent<>
	{
	};

	/** A screen was loaded*/
	template <>
	struct LvEvent<LV_EVENT_SCREEN_LOADED> : public LvEvent<>
	{
	};

	/** A screen was unloaded*/
	template <>
	struct LvEvent<LV_EVENT_SCREEN_UNLOADED> : public LvEvent<>
	{
	};

	/** Object coordinates/size have changed*/
	template <>
	struct LvEvent<LV_EVENT_SIZE_CHANGED> : public LvEvent<>
	{
		/**
		 * Get the old area of the object before its size was changed. Can be used in `LV_EVENT_SIZE_CHANGED`
		 * @return      the old absolute area of the object or NULL if called on an unrelated event
		 */
		const lv_area_t &getOldSize() noexcept { return *lv_event_get_old_size(this); };
	};

	/** Object's style has changed*/
	template <>
	struct LvEvent<LV_EVENT_STYLE_CHANGED> : public LvEvent<>
	{
	};

	/** The children position has changed due to a layout recalculation*/
	template <>
	struct LvEvent<LV_EVENT_LAYOUT_CHANGED> : public LvEvent<>
	{
	};

	/** Get the internal size of a widget*/
	template <>
	struct LvEvent<LV_EVENT_GET_SELF_SIZE> : public LvEvent<>
	{
		/**
		 * Get a pointer to an `lv_point_t` variable in which the self size should be saved (width in `point->x` and height `point->y`).
		 * Can be used in `LV_EVENT_GET_SELF_SIZE`
		 * @return      pointer to `lv_point_t` or NULL if called on an unrelated event
		 */
		lv_point_t &getSelfSizeInfo() noexcept { return *lv_event_get_self_size_info(this); };
	};

	/** Input device events*/

	/** The object has been pressed*/
	using LvEventPressed = LvEvent<LV_EVENT_PRESSED>;

	/** The object is being pressed (called continuously while pressing)*/
	using LvEventPressing = LvEvent<LV_EVENT_PRESSING>;

	/** The object is still being pressed but slid cursor/finger off of the object */
	using LvEventPressLost = LvEvent<LV_EVENT_PRESS_LOST>;

	/** The object was pressed for a short period of time, then released it. Not called if scrolled.*/
	using LvEventShortClicked = LvEvent<LV_EVENT_SHORT_CLICKED>;

	/** Object has been pressed for at least `long_press_time`.  Not called if scrolled.*/
	using LvEventLongPressed = LvEvent<LV_EVENT_LONG_PRESSED>;

	/** Called after `long_press_time` in every `long_press_repeat_time` ms.  Not called if scrolled.*/
	using LvEventLongPressedRepeat = LvEvent<LV_EVENT_LONG_PRESSED_REPEAT>;

	/** Called on release if not scrolled (regardless to long press)*/
	using LvEventClicked = LvEvent<LV_EVENT_CLICKED>;

	/** Called in every cases when the object has been released*/
	using LvEventReleased = LvEvent<LV_EVENT_RELEASED>;

	/** Scrolling begins*/
	using LvEventScrollBegin = LvEvent<LV_EVENT_SCROLL_BEGIN>;

	/** Scrolling ends*/
	using LvEventScrollEnd = LvEvent<LV_EVENT_SCROLL_END>;

	/** Scrolling*/
	using LvEventScroll = LvEvent<LV_EVENT_SCROLL>;

	/** A gesture is detected. Get the gesture with `lv_indev_get_gesture_dir(lv_indev_get_act());` */
	using LvEventGesture = LvEvent<LV_EVENT_GESTURE>;

	/** A key is sent to the object. Get the key with `lv_indev_get_key(lv_indev_get_act());`*/
	using LvEventKey = LvEvent<LV_EVENT_KEY>;

	/** The object is focused*/
	using LvEventFocused = LvEvent<LV_EVENT_FOCUSED>;

	/** The object is defocused*/
	using LvEventDefocused = LvEvent<LV_EVENT_DEFOCUSED>;

	/** The object is defocused but still selected*/
	using LvEventLeave = LvEvent<LV_EVENT_LEAVE>;

	/** Perform advanced hit-testing*/
	using LvEventHitTest = LvEvent<LV_EVENT_HIT_TEST>;

	/** Drawing events*/

	/** Check if the object fully covers an area. The event parameter is `lv_cover_check_info_t *`.*/
	using LvEventCoverCheck = LvEvent<LV_EVENT_COVER_CHECK>;

	/** Get the required extra draw area around the object (e.g. for shadow). The event parameter is `lv_coord_t *` to store the size.*/
	using LvEventRefrExtDrawSize = LvEvent<LV_EVENT_REFR_EXT_DRAW_SIZE>;

	/** Starting the main drawing phase*/
	using LvEventDrawMainBegin = LvEvent<LV_EVENT_DRAW_MAIN_BEGIN>;

	/** Perform the main drawing*/
	using LvEventDrawMain = LvEvent<LV_EVENT_DRAW_MAIN>;

	/** Finishing the main drawing phase*/
	using LvEventDrawMainEnd = LvEvent<LV_EVENT_DRAW_MAIN_END>;

	/** Starting the post draw phase (when all children are drawn)*/
	using LvEventDrawPostBegin = LvEvent<LV_EVENT_DRAW_POST_BEGIN>;

	/** Perform the post draw phase (when all children are drawn)*/
	using LvEventDrawPost = LvEvent<LV_EVENT_DRAW_POST>;

	/** Finishing the post draw phase (when all children are drawn)*/
	using LvEventDrawPostEnd = LvEvent<LV_EVENT_DRAW_POST_END>;

	/** Starting to draw a part. The event parameter is `lv_obj_draw_dsc_t *`. */
	using LvEventDrawPartBegin = LvEvent<LV_EVENT_DRAW_PART_BEGIN>;

	/** Finishing to draw a part. The event parameter is `lv_obj_draw_dsc_t *`. */
	using LvEventDrawPartEnd = LvEvent<LV_EVENT_DRAW_PART_END>;

	/** Special events*/

	/** The object's value has changed (i.e. slider moved)*/
	using LvEventValueChanged = LvEvent<LV_EVENT_VALUE_CHANGED>;

	/** A text is inserted to the object. The event data is `char *` being inserted.*/
	using LvEventInsert = LvEvent<LV_EVENT_INSERT>;

	/** Notify the object to refresh something on it (for the user)*/
	using LvEventRefresh = LvEvent<LV_EVENT_REFRESH>;

	/** A process has finished*/
	using LvEventReady = LvEvent<LV_EVENT_READY>;

	/** A process has been cancelled */
	using LvEventCancel = LvEvent<LV_EVENT_CANCEL>;

	/** Other events*/

	/** Object is being deleted*/
	using LvEventDelete = LvEvent<LV_EVENT_DELETE>;

	/** Child was removed, added, or its size, position were changed */
	using LvEventChildChanged = LvEvent<LV_EVENT_CHILD_CHANGED>;

	/** Child was created, always bubbles up to all parents*/
	using LvEventChildCreated = LvEvent<LV_EVENT_CHILD_CREATED>;

	/** Child was deleted, always bubbles up to all parents*/
	using LvEventChildDeleted = LvEvent<LV_EVENT_CHILD_DELETED>;

	/** A screen unload started, fired immediately when scr_load is called*/
	using LvEventScreenUnloadStart = LvEvent<LV_EVENT_SCREEN_UNLOAD_START>;

	/** A screen load started, fired when the screen change delay is expired*/
	using LvEventScreenLoadStart = LvEvent<LV_EVENT_SCREEN_LOAD_START>;

	/** A screen was loaded*/
	using LvEventScreenLoaded = LvEvent<LV_EVENT_SCREEN_LOADED>;

	/** A screen was unloaded*/
	using LvEventScreenUnloaded = LvEvent<LV_EVENT_SCREEN_UNLOADED>;

	/** Object coordinates/size have changed*/
	using LvEventSizeChanged = LvEvent<LV_EVENT_SIZE_CHANGED>;

	/** Object's style has changed*/
	using LvEventStyleChanged = LvEvent<LV_EVENT_STYLE_CHANGED>;

	/** The children position has changed due to a layout recalculation*/
	using LvEventLayoutChanged = LvEvent<LV_EVENT_LAYOUT_CHANGED>;

	/** Get the internal size of a widget*/
	using LvEventGetSelfSize = LvEvent<LV_EVENT_GET_SELF_SIZE>;

} // namespace lvglpp

#endif /* LVCALLBACK_H_ */
